package com.sikulifang.bank.simulation.service.bank;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import cn.hutool.db.PageResult;
import cn.hutool.db.handler.HandleHelper;
import cn.hutool.db.handler.RsHandler;
import cn.hutool.db.sql.Condition;
import cn.hutool.db.sql.SqlBuilder;
import com.google.common.collect.Lists;
import com.sikulifang.bank.simulation.dto.TransferPaymentDTO;
import com.sikulifang.bank.simulation.service.IAccountService;
import com.sikulifang.bank.simulation.service.IBankMessage;
import com.sikulifang.bank.simulation.service.IBankService;
import com.sikulifang.bank.simulation.service.IDetailService;
import com.sikulifang.bank.simulation.util.AsyncUtil;
import com.sikulifang.bank.simulation.util.DataUtil;
import com.sikulifang.bank.simulation.util.R;
import com.sikulifang.bank.simulation.util.TemplateUtils;
import com.sikulifang.frequency.core.InterfaceTypeEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

import static com.sikulifang.bank.simulation.constant.CodeConstants.PARAMETER_ERROR;
import static com.sikulifang.bank.simulation.constant.TableConstants.ACCOUNT_TABLE_NAME;
import static com.sikulifang.bank.simulation.constant.TableConstants.DETAIL_TABLE_NAME;

@Slf4j
public abstract class AbstractBankMessage implements IBankMessage {

    /**
     * 缓存 账号  金额
     */
    private static final ConcurrentHashMap<String, BigDecimal> cacheMoneyMap = new ConcurrentHashMap<>();
    protected Db db;
    protected IDetailService detailService;
    protected IAccountService accountService;
    protected IBankService bankService;
    protected TemplateUtils templateUtils;

    @SneakyThrows
    public R<Dict> tradingEntrance(List<TransferPaymentDTO> transferPaymentList) {
        Dict dict = new Dict();

        TransferPaymentDTO paymentDTO = transferPaymentList.get(0);

        String batchSerialNo = paymentDTO.getBatchSerialNo();

        if (StrUtil.isBlank(batchSerialNo)) {
            log.error("流水号不能为空");
            return R.restResult(dict, PARAMETER_ERROR, "流水号不能为空");
        }

        long count = detailService.findCountBySerialNo(batchSerialNo);
        if (count > 0) {
            log.error("流水号重复");
            return R.restResult(dict, PARAMETER_ERROR, "当前交易正在处理中，请勿重复发起");
        }

        List<Dict> successData = new ArrayList<>();
        List<Dict> errorData = new ArrayList<>();

        // 普通转账
        ArrayList<TransferPaymentDTO> ordinary = Lists.newArrayList();
        // 跨行转账
        ArrayList<TransferPaymentDTO> enjambment = Lists.newArrayList();
        // 大额转账
        ArrayList<TransferPaymentDTO> largeAmount = Lists.newArrayList();

        // 所有的账号
        Set<String> accountSet = new HashSet<>();

        // 查询失败率
        double failureRateByCode = bankService.getFailureRateByCode(bankCode());
        int size = (int) (transferPaymentList.size() * failureRateByCode);

        List<TransferPaymentDTO> temporaryList = new ArrayList<>(transferPaymentList);
        Random random = new Random();

        for (TransferPaymentDTO payment : transferPaymentList) {
            accountSet.add(payment.getPayAccountNo());
            accountSet.add(payment.getRecAccountNo());
        }

        List<Entity> list = db.find(Entity.create(ACCOUNT_TABLE_NAME).set("card_id", accountSet));
        // 账号信息
        Map<String, Entity> accountMap = list.stream().collect(
                Collectors.toMap(
                        k -> k.getStr("card_id"), v -> v
                )
        );

        // 临时数据
        List<Entity> bactList = new ArrayList<>();

        Date date = new Date();
        for (TransferPaymentDTO payment : transferPaymentList) {

            payment.setBankCode(bankCode());

            Dict resDict = new Dict();
            resDict.set("serialNo", payment.getSerialNo());

            String status = "0";
            String statusText = "交易处理中";

            if (StrUtil.equals(payment.getPayAccountNo(), payment.getRecAccountNo())) {
                status = "2";
                statusText = "付款人账号和付款人账号不能相同";
                errorData.add(resDict);
            } else {

                // 根据失败率 过滤失败的交易，先筛选出成功，剩下的就是失败的交易
                if (temporaryList.size() > size) {
                    temporaryList.remove(random.nextInt(temporaryList.size()));
                } else {
                    payment.setError(Boolean.TRUE);
                }
                // 获取收付款人的账号信息
                Entity payEntity = accountMap.get(payment.getPayAccountNo());
                Entity recEntity = accountMap.get(payment.getRecAccountNo());

                if (null == payEntity || null == recEntity) {
                    status = "2";
                    statusText = "付款人账号或付款人账号错误";
                    errorData.add(resDict);
                } else {
                    BigDecimal money = payment.getMoney();
                    if (money.compareTo(new BigDecimal("50000")) > 0) {
                        largeAmount.add(payment);
                        successData.add(resDict);
                    } else if (!payEntity.getStr("bank_code").equals(recEntity.getStr("bank_code"))) {
                        enjambment.add(payment);
                        successData.add(resDict);
                    } else {
                        ordinary.add(payment);
                    }
                }
            }
            resDict.set("money", payment.getMoney());
            resDict.set("status", status);
            resDict.set("statusText", statusText);

            Entity entity = Entity.create(DETAIL_TABLE_NAME)
                    .set("money", payment.getMoney())
                    .set("post_script", payment.getPostScript())
                    .set("summary", payment.getSummary())
                    .set("serial_no", payment.getSerialNo())
                    .set("batch_serial_no", payment.getBatchSerialNo())
                    .set("additional", payment.getAdditional())
                    .set("account_no", payment.getPayAccountNo())
                    .set("opp_account_no", payment.getRecAccountNo())
                    .set("status", status)
                    .set("status_text", statusText)
                    .set("type", 3)
                    .set("balance", 0)
                    .set("flag", "D")
                    .set("bank_code", payment.getBankCode())
                    .set("create_time", date);
            bactList.add(entity);

        }

        db.insert(bactList);
        ThreadPoolExecutor poolExecutor = AsyncUtil.getThreadPoolExecutor();

        if (!enjambment.isEmpty()) {
            AsyncUtil.putIntoQueue(timeout -> poolExecutor.execute(
                    () -> transaction(accountMap, enjambment)
            ), 3);
        }

        if (!largeAmount.isEmpty()) {
            // 大额转账 延时 30S
            AsyncUtil.putIntoQueue(timeout -> poolExecutor.execute(
                    () -> transaction(accountMap, largeAmount)
            ), 30);
        }

        // 普通转账
        if (!ordinary.isEmpty()) {
            transaction(accountMap, ordinary);

            Set<String> serialNoSet =
                    ordinary.stream().map(TransferPaymentDTO::getSerialNo).collect(Collectors.toSet());

            List<Entity> bySerialNo = detailService.findBySerialNo(serialNoSet, batchSerialNo);
            for (Entity entity : bySerialNo) {

                Dict resDict = new Dict();
                resDict.set("money", entity.get("money"));
                resDict.set("serialNo", entity.get("serial_no"));
                resDict.set("status", entity.getInt("status"));
                resDict.set("statusText", entity.get("message", ""));
                successData.add(resDict);

            }
        }

        dict.put("success", successData);
        dict.put("error", errorData);
        return R.ok(dict);
    }


    @SneakyThrows
    public void saveDetail(List<Object[]> bactList) {
        if (bactList.isEmpty()) {
            return;
        }

        Set<Object> batchSerialNoSet = new HashSet<>();
        // Set<Object> serialNoSet = new HashSet<>();
        for (Object[] objects : bactList) {
            batchSerialNoSet.add(objects[16]);
            // serialNoSet.add(objects[15]);
        }

        // 根据批量流水号，删除临时添加的数据
        Entity delEntity = Entity.create(DETAIL_TABLE_NAME)
                .set("batch_serial_no", batchSerialNoSet)
                .set("status", 0);
        db.del(delEntity);

        String batchSql = """
                INSERT INTO `detail` (`account_no`, `account_name`, `bank_name`, `balance`, `opp_account_no`, `bank_no`,
                `opp_account_name`, `opp_bank_name`, `opp_bank_no`, `type`, `money`, `flag`, `post_script`, `summary`,
                `use_cn`, `serial_no`, `batch_serial_no`, `status`, `status_text`, `tran_date`, `tran_time`, `create_time`,
                `additional`, `bank_code`)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """;
        db.executeBatch(batchSql, bactList);

    }

    @SneakyThrows
    public void transaction(Map<String, Entity> accountMap, List<TransferPaymentDTO> paymentList) {

        if (paymentList.isEmpty()) {
            return;
        }
        List<Object[]> detailList = new ArrayList<>();

        List<Object[]> updateList = new ArrayList<>();

        for (TransferPaymentDTO payment : paymentList) {
            detailList.addAll(transaction(payment, accountMap));
        }

        String sql = "UPDATE `account` SET `money` = ? where `card_id` =?";

        for (String account : accountMap.keySet()) {
            updateList.add(
                    new Object[]{getMoney(accountMap.get(account)), account}
            );
        }

        db.executeBatch(sql, updateList);

        saveDetail(detailList);
    }

    /**
     * 转账
     *
     * @param payment 单笔交易的信息
     */
    public List<Object[]> transaction(TransferPaymentDTO payment, Map<String, Entity> accountMap) {

        List<Object[]> bactList = new ArrayList<>();

        int status;
        String statusText;

        Dict parerDict = getDict(payment);
        parerDict.put("flag", "D");

        // 交易的金额
        BigDecimal money = payment.getMoney();

        // 付款人账号
        String payerAccount = payment.getPayAccountNo();
        // 收款人账号
        String recAccountNo = payment.getRecAccountNo();
        synchronized (DETAIL_TABLE_NAME) {
            try {
                // 自己的账号信息
                parerDict.put("card_id", payerAccount);
                // 对方账号信息
                parerDict.put("oppAccountNo", recAccountNo);

                // 查询付款人信息
                Entity parerEntity = accountMap.get(payerAccount);

                if (parerEntity == null) {
                    status = 2;
                    statusText = "付款人账号错误";

                    parerDict.put("status", status);
                    parerDict.put("statusText", statusText);
                    return bactList;
                }

                // 付款人的余额
                BigDecimal parerMoney = getMoney(parerEntity);

                // 查询收款人信息
                Entity recEntity = accountMap.get(recAccountNo);
                if (recEntity == null) {
                    status = 2;
                    statusText = "收款人账号错误";

                    parerDict.put("status", status);
                    parerDict.put("statusText", statusText);
                    return bactList;
                }

                // 收款人的余额
                BigDecimal recMoney = getMoney(recEntity);

                String recAccountName = recEntity.getStr("account_name");
                String recOpenBankName = recEntity.getStr("open_bank_name");

                parerDict.put("oppAccountName", recAccountName);
                parerDict.put("oppOpenBankName", recOpenBankName);
                parerDict.put("oppBankNo", recEntity.get("open_bank_no", ""));

                parerDict.put("bankNo", parerEntity.get("open_bank_no", ""));
                parerDict.put("account_name", parerEntity.get("account_name", ""));
                parerDict.put("open_bank_name", parerEntity.get("open_bank_name", ""));
                // 先给予旧的余额 如果转账没有失败，在给与新的余额
                parerDict.put("balance", parerMoney);

                //  根据系统配置的失败率抛出异常
                if (payment.getError()) {
                    status = 2;
                    statusText = "系统异常";

                    parerDict.put("status", status);
                    parerDict.put("statusText", statusText);
                    return bactList;
                }

                if (money.compareTo(parerMoney) > 0) {
                    status = 2;
                    statusText = "付款人账号余额不足";

                    parerDict.put("status", status);
                    parerDict.put("statusText", statusText);
                    return bactList;
                }

                BigDecimal parerMoneyNew = NumberUtil.sub(parerMoney, money);

                // 更新付款人 的金额
                cacheMoneyMap.put(payerAccount, parerMoneyNew);

                Dict recDict = getDict(payment);
                recDict.put("flag", "C");

                BigDecimal recMoneyNew = NumberUtil.add(recMoney, money);

                // 更新收款人 的金额
                cacheMoneyMap.put(recAccountNo, recMoneyNew);

                // 自己的账号信息
                recDict.put("card_id", recAccountNo);
                recDict.put("account_name", recAccountName);
                recDict.put("open_bank_name", recOpenBankName);
                recDict.put("balance", recMoneyNew);
                recDict.put("bankNo", recEntity.get("open_bank_no", ""));
                // 对方账号信息
                recDict.put("oppAccountNo", payerAccount);
                recDict.put("oppAccountName", parerEntity.get("account_name", ""));
                recDict.put("oppOpenBankName", parerEntity.get("open_bank_name", ""));
                recDict.put("oppBankNo", parerEntity.get("open_bank_no", ""));

                // 存款、收款 添加明细
                String bankCode = recEntity.getStr("bank_code");
                bactList.add(
                        detailService.updateDetail(recDict, bankCode.split(StrPool.DASHED)[0])
                );

                // 更新付款人的余额
                parerDict.put("balance", parerMoney);

            } finally {
                // 取款、付款账号减钱
                bactList.add(
                        detailService.updateDetail(parerDict, payment.getBankCode())
                );
            }
        }

        return bactList;
    }

    @Override
    public R<Dict> queryBalance(String account) {
        // 根据账号查询信息
        Entity entity = accountService.getDataByKey(new Dict().set("card_id", account));
        if (entity == null) {
            return R.failed("账号不存在");
        }
        return R.ok(DataUtil.toCamelCase(entity));
    }

    @Override
    public R<Dict> queryDetail(Dict request) {
        return null;
    }

    @Override
    @SneakyThrows
    public List<Dict> query(String sql) {
        List<Entity> list = db.query(sql);
        return DataUtil.toCamelCase(list);
    }

    @Override
    public String getActionType(String message) {
        String actionName = getActionName(message);
        return getType(actionName);
    }

    /**
     * Description: 根据接口名称，获取接口的类型
     * <p>
     * Date: 2023年7月20日, 0020 14:09
     *
     * @param actionName 接口名称
     * @return {@link InterfaceTypeEnum}
     */
    protected abstract String getType(String actionName);

    @Override
    public String prohibitAccess(String body) {
        return body;
    }


    /**
     * 根据分页参数构建分页对象
     */
    public Page getRequestPage(int pageNumber, int pageSize) {
        return Page.of(pageNumber, pageSize);
    }

    protected static SqlBuilder getSqlBuilder(String account, BigDecimal minAmt, BigDecimal maxAmt,
                                              String startDate, String endDate) {
        SqlBuilder builder = SqlBuilder.create().select("*")
                .from(DETAIL_TABLE_NAME)
                .where(new Condition("account_no", account));

        if (ObjectUtil.isNotNull(minAmt)) {
            builder.append(" and money >=" + minAmt);
        }
        if (ObjectUtil.isNotNull(maxAmt)) {
            builder.append(" and money <=" + maxAmt);
        }

        if (ObjectUtil.isNotNull(startDate)) {
            builder.append(" and  tran_date >=" + startDate);
        }
        if (ObjectUtil.isNotNull(endDate)) {
            builder.append(" and tran_date <=" + endDate);
        }

        return builder;
    }


    private Dict getDict(TransferPaymentDTO payment) {
        Dict dict = new Dict();
        dict.put("status", 1);
        dict.put("statusText", "交易成功");
        dict.put("money", payment.getMoney());
        dict.set("flag", payment.getFlag());
        dict.set("postScript", payment.getPostScript());
        dict.set("summary", payment.getSummary());
        dict.set("useCn", payment.getUseCn());
        dict.set("serialNo", payment.getSerialNo());
        dict.set("batchSerialNo", payment.getBatchSerialNo());
        dict.set("additional", payment.getAdditional());

        return dict;
    }

    private BigDecimal getMoney(Entity entity) {
        // 获取账号
        String cardId = entity.getStr("card_id");
        BigDecimal money;

        if (cacheMoneyMap.containsKey(cardId)) {
            money = cacheMoneyMap.get(cardId);
        } else {
            money = entity.getBigDecimal("money");
            cacheMoneyMap.put(cardId, money);
        }
        return money;
    }

    @SneakyThrows
    public <S> PageResult<S> page(CharSequence sql, Page page, Class<S> beanType, Object... params) {
        return db.page(
                sql, page,
                (RsHandler<? extends PageResult<S>>) rs -> HandleHelper.handleRsToBeanList(
                        rs,
                        new PageResult<>(
                                page.getPageNumber(), page.getPageSize(), (int) db.count(sql, params)
                        ),
                        beanType
                ), params
        );
    }

    @Autowired
    public void setDb(Db db) {
        this.db = db;
    }

    @Autowired
    public void setDetailService(IDetailService detailService) {
        this.detailService = detailService;
    }

    @Autowired
    public void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    @Autowired
    public void setBankService(IBankService bankService) {
        this.bankService = bankService;
    }

    @Autowired
    public void setTemplateUtils(TemplateUtils templateUtils) {
        this.templateUtils = templateUtils;
    }
}
